package com.gluck.cases.app.biz.impl.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.gluck.cases.app.biz.impl.TestCaseService;
import com.gluck.cases.app.biz.impl.excel.*;
import com.gluck.cases.common.utils.constants.BizSerConstant;
import com.gluck.cases.common.utils.enums.BizSerErrorEnum;
import com.gluck.cases.common.utils.annotations.NoScanClass;
import com.gluck.cases.common.utils.utils.AssertUtil;
import com.gluck.cases.common.utils.utils.ScanPackageUtils;
import com.gluck.cases.core.block.CasesApiBlock;
import com.gluck.cases.core.modal.assemble.CasesApiAssemble;
import com.gluck.cases.core.modal.dto.TestCaseDTO;
import com.gluck.cases.core.modal.entity.CasesApi;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @ClassName TestCaseServiceImpl
 * @Desc TestCaseServiceImpl 测试用例service实现类
 * @Author shiyongzhe
 * @Date 2022/5/23 - 10:14 下午
 **/
@Service
public class TestCaseServiceImpl implements TestCaseService {

    @Autowired
    private CasesApiBlock casesApiBlock;

    private static String[] baseTypes = {
            "java.lang.Integer",
            "java.lang.Double",
            "java.lang.Float",
            "java.lang.Long",
            "java.lang.Short",
            "java.lang.Byte",
            "java.lang.Boolean",
            "java.lang.Character",
            "java.lang.String",
            "int",
            "double",
            "long",
            "short",
            "byte",
            "boolean",
            "char",
            "float"
//            "javax.servlet.http.HttpServletRequest",
//            "javax.servlet.http.HttpServletResponse"
    };

    @Override
    public List<TestCaseDTO> importCasesExcel2List() throws IOException {
        // step 1 扫描识别excel 文件
        File file = new File(BizSerConstant.CASES_EXCEL_PATH);
        if(!file.isFile()){
            AssertUtil.isTrue(true, BizSerErrorEnum.FILE_NOT_EXISTS);
        }
        FileInputStream fileInputStream = new FileInputStream(file);
        HSSFWorkbook wb = new HSSFWorkbook(fileInputStream);
        // step 2 构建excel责任链
        List<TestCaseDTO> caseEntities = excuteExcelHandler(wb);
        // step 3 返回数据
        return caseEntities;
    }

    private List<TestCaseDTO> excuteExcelHandler(HSSFWorkbook wb) throws IOException {
        AbstractExcelHandler.Builder<Workbook> excelBuilder = new AbstractExcelHandler.Builder<>();
        excelBuilder.setSheetname(BizSerConstant.TEST_CASE_SHEET_NAME).setInvokeEntity(TestCaseDTO.class);
        excelBuilder.addHandler(new AnalysisExcelHandler());
        excelBuilder.build().doHandler(wb, excelBuilder);
        List<TestCaseDTO> caseEntities = excelBuilder.getCaseEntities();
        return caseEntities;
    }

    @Override
    public boolean insertCases2Db(List<TestCaseDTO> dtos) {
        dtos.stream().forEach(x->{
            CasesApi casesApi = CasesApiAssemble.TestCaseDTOConvertCasesApi(x);
            casesApiBlock.insert(casesApi);
        });
        return true;
    }

    @Override
    public boolean insertCasesRecord(List<TestCaseDTO> dtos) {
        return false;
    }

    @Override
    public Workbook downloadTestCase2Excel(List<TestCaseDTO> dtos) throws IOException {
        HSSFWorkbook wb = new HSSFWorkbook();
        // step 1 构建excel责任链
        AbstractExcelHandler.Builder<Workbook> excelBuilder = new AbstractExcelHandler.Builder<>();
        excelBuilder.setCases(dtos).setSheetname(BizSerConstant.TEST_CASE_SHEET_NAME).setWbname(BizSerConstant.TEST_CASE_WB_NAME);
        excelBuilder
                .addHandler(new CreateSheetHandler())
                .addHandler(new CreateRowsHandler())
                .addHandler(new ExcelOutputHandler())
        ;
        excelBuilder.build().doHandler(wb, excelBuilder);
        Workbook workbook = excelBuilder.getWb();
        // step 2 返回excel wb 对象
        return workbook;
    }

    @Override
    public List<TestCaseDTO> getAllApiData(List<String> needTestPath) {
        // step 1 获取有所有的包
        Set<Class<?>> classes = ScanPackageUtils.startScan(BizSerConstant.SYNC_PACK_PATH);
        // step 2 转list
        List<Class<?>> collect = classes.stream().collect(Collectors.toList());
        // step 3 没有抓取到包直接异常抛出
        AssertUtil.isTrue(Objects.isNull(collect) || collect.size() == 0, BizSerErrorEnum.SCAN_PACKAGE_NOT_FOUND);
        // step 4 过滤数据并转换
        List<TestCaseDTO> list = buildData(collect).stream().filter(x->{
            return filterCases(needTestPath, x.getApiPath());
        }).collect(Collectors.toList());
        return list;
    }

    /**
     * 功能描述: 过滤没有指定的测试单列url 如果needTestPath没有数据则不过滤
     * @Params [needTestPath, path]
     * @Return boolean
     * @Author I`m Gluck
     * @Date 2022/5/28
     */
    private boolean filterCases(List<String> needTestPath, String path){
        if(needTestPath.size() == 0){
            return true;
        }
        if(needTestPath.contains(path.trim())){
            return true;
        }
        return false;
    }

    private List<TestCaseDTO> buildData(List<Class<?>> collect) {
        List<TestCaseDTO> testCaseDTOList = collect
                .stream()
                .filter(
                        // 过滤没有RestController 注解的类
                        x -> filterNoHaveRestController(x)
                ).filter(
                        // 过滤不扫描的controller
                        controllers -> filterNeedScanClass(controllers)
                ).flatMap(methods ->
                        // 组装method 信息
                        queryMethods(methods.getMethods(), methods).stream()
                )
                .collect(Collectors.toList());
        return testCaseDTOList;
    }

    private List<TestCaseDTO> queryMethods(Method[] methods, Class<?> clazz) {
        AtomicInteger index = new AtomicInteger(0);
        List<TestCaseDTO> collect = Arrays.stream(methods).filter(x ->
                filterNoHaveMapping(x)
        ).map(y -> {
            // 获取下标 设置为测试编号
            int i = 1 + index.getAndIncrement();
            // step 1 获取控制器接口信息
            TestCaseDTO controllerInfo = getRequestMappingInfo(clazz.getAnnotation(RequestMapping.class), new TestCaseDTO());
            // step 2 获取请求方法接口信息
            TestCaseDTO dto = new TestCaseDTO();
            TestCaseDTO methodInfo = getMethodInfo(y, dto);
            // step 3 获取请求参数信息
            methodInfo = getMethodParamsInfo(y, methodInfo);
            methodInfo.setApiPath(controllerInfo.getApiPath() + methodInfo.getApiPath());
            methodInfo.setTestName(controllerInfo.getCaseName() + ":" + methodInfo.getCaseName());
            methodInfo.setCaseNo(controllerInfo.getCaseName() + ":" + String.valueOf(i));
            return methodInfo;
        }).collect(Collectors.toList());
        return collect;
    }

    private TestCaseDTO getMethodParamsInfo(Method m, TestCaseDTO methodInfo) {
        Parameter[] parameters = m.getParameters();
        ArrayList<HashMap<String, Object>> obj = new ArrayList<>();
        List<String> paramsJson = Arrays.stream(parameters).map(x -> {
            Class<?>[] parameterTypes = x.getDeclaringExecutable().getParameterTypes();
            RequestParam requestParam = x.getAnnotation(RequestParam.class);
            if(!Objects.isNull(requestParam)){
                HashMap<String, Object> map = new HashMap<>();
                map.put("name", requestParam.name());
                map.put("required",  requestParam.required());
                map.put("defaultValue", requestParam.defaultValue());
                obj.add(map);
            }
            String prams = instanceParamsReq(parameterTypes);
            return prams;
        }).collect(Collectors.toList());

        setMethodParams(methodInfo, paramsJson, obj);
        return methodInfo;
    }

    /**
     * 功能描述: 设置请求参数
     * @Params [methodInfo, paramsJson, obj]
     * @Return com.gluck.stock.oauth.core.modal.dto.TestCaseDTO
     * @Author I`m Gluck
     * @Date 2022/5/25
     */
    private TestCaseDTO setMethodParams(TestCaseDTO methodInfo, List<String> paramsJson, ArrayList<HashMap<String, Object>> obj) {
        methodInfo.setParams("");
        // 使用普通请求实体直接设置并返回
        if(paramsJson.size() > 0){
            methodInfo.setParams(paramsJson.get(0));
        }
        // 使用@RequestParam 注解请款
        if(obj.size() > 0){
            String[] split = paramsJson.get(0).split(";");
            String js = "{";
            for (int i = 0; i < split.length; i ++){
                HashMap<String, Object> mvalue = obj.get(i);
                String isRequired = (Boolean) mvalue.get("required") ? "必填" : "非必填";
                js += mvalue.get("name") + ":" + split[i] + "," + "required:" + isRequired + ",";
            }
            String substring = js.substring(0, js.length() - 1);
            substring = substring + "}";
            methodInfo.setParams(substring);
        }
        return methodInfo;
    }

    private String instanceParamsReq(Class<?>[] parameterTypes) {
        if (parameterTypes.length == 0) {
            return "";
        }
        String json = Arrays.stream(parameterTypes).map(p -> {
            // 反射请求类实体
            String s = invokeReq(p);
            return s;
        }).collect(Collectors.joining());
        return json;
    }

    private String invokeReq(Class<?> clazz) {
        try {
            Class<?> aClass = Class.forName(clazz.getName());
            boolean contains = Arrays.asList(baseTypes).contains(clazz.getName());
            if (contains) {
                return getSingleParams(clazz);
            }
            Object o = aClass.getConstructor().newInstance();
            String s = JSONObject.toJSONString(o, SerializerFeature.WriteMapNullValue);
            return s;
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            AssertUtil.isTrue(true, BizSerErrorEnum.SYSTEM_ERROR, e.getMessage());
        } catch (InvocationTargetException e) {
            AssertUtil.isTrue(true, BizSerErrorEnum.SYSTEM_ERROR, e.getMessage());
        } catch (InstantiationException e) {
            AssertUtil.isTrue(true, BizSerErrorEnum.SYSTEM_ERROR, e.getMessage());
        } catch (IllegalAccessException e) {
            AssertUtil.isTrue(true, BizSerErrorEnum.SYSTEM_ERROR, e.getMessage());
        }
        return "";
    }

    private String getSingleParams(Class<?> clazz) {
        String param = clazz.getName();
        if (!Objects.isNull(clazz.getAnnotation(RequestParam.class))) {
            String name = clazz.getAnnotation(RequestParam.class).name();
            param = name + ":" + clazz.getName();
        }
        return param + ";";
    }

    private TestCaseDTO getMethodInfo(Method y, TestCaseDTO dto) {
        if (!Objects.isNull(y.getAnnotation(RequestMapping.class))) {
            RequestMapping requestMapping = y.getAnnotation(RequestMapping.class);
            dto = getRequestMappingInfo(requestMapping, dto);
        }
        if (!Objects.isNull(y.getAnnotation(PostMapping.class))) {
            PostMapping postMapping = y.getAnnotation(PostMapping.class);
            dto = getPostMappingInfo(postMapping, dto);
        }
        if (!Objects.isNull(y.getAnnotation(GetMapping.class))) {
            GetMapping getMapping = y.getAnnotation(GetMapping.class);
            dto = getGetMappingInfo(getMapping, dto);
        }
        return dto;
    }

    private TestCaseDTO getRequestMappingInfo(RequestMapping requestMapping, TestCaseDTO dto) {
        if (!Objects.isNull(requestMapping.value()) && requestMapping.value().length > 0) {
            dto.setApiPath(requestMapping.value()[0]);
        }
        if (!Objects.isNull(requestMapping.path()) && requestMapping.path().length > 0) {
            dto.setApiPath(requestMapping.path()[0]);
        }
        if (!Objects.isNull(requestMapping.method()) && requestMapping.method().length > 0) {
            dto.setRequestMethod(requestMapping.method()[0].name());
        }
        // 没有标明method 则标注post get 都可以请求
        if (Objects.isNull(requestMapping.method()) || requestMapping.method().length == 0) {
            dto.setRequestMethod("GET/POST");
        }
        // 设置requestmapping name
        String name = StringUtils.isEmpty(requestMapping.name()) ? "" : requestMapping.name();
        dto.setCaseName(name);
        return dto;
    }

    private TestCaseDTO getPostMappingInfo(PostMapping postMapping, TestCaseDTO dto) {
        if (!Objects.isNull(postMapping.value()) && postMapping.value().length > 0) {
            dto.setApiPath(postMapping.value()[0]);
        }
        if (!Objects.isNull(postMapping.value()) && postMapping.path().length > 0) {
            dto.setApiPath(postMapping.path()[0]);
        }
        // postMapping name
        String name = StringUtils.isEmpty(postMapping.name()) ? "" : postMapping.name();
        dto.setCaseName(name);
        dto.setRequestMethod("POST");
        return dto;
    }

    private TestCaseDTO getGetMappingInfo(GetMapping getMapping, TestCaseDTO dto) {
        if (!Objects.isNull(getMapping.value()) && getMapping.value().length > 0) {
            dto.setApiPath(getMapping.value()[0]);
        }
        if (!Objects.isNull(getMapping.value()) && getMapping.value().length > 0) {
            dto.setApiPath(getMapping.value()[0]);
        }
        // getMapping name
        String name = StringUtils.isEmpty(getMapping.name()) ? "" : getMapping.name();
        dto.setCaseName(name);
        dto.setRequestMethod("GET");
        return dto;
    }

    private boolean filterNoHaveMapping(Method x) {
        RequestMapping requestMapping = x.getAnnotation(RequestMapping.class);
        PostMapping postMapping = x.getAnnotation(PostMapping.class);
        GetMapping getMapping = x.getAnnotation(GetMapping.class);
        if (!Objects.isNull(requestMapping) || !Objects.isNull(postMapping) || !Objects.isNull(getMapping)) {
            return true;
        }
        return false;
    }

    private boolean filterNoHaveRestController(Class<?> x) {
        RestController restController = x.getAnnotation(RestController.class);
        if (Objects.isNull(restController)) {
            return false;
        }
        return true;
    }

    private boolean filterNeedScanClass(Class<?> x){
        NoScanClass noScanClass = x.getAnnotation(NoScanClass.class);
        if (Objects.isNull(noScanClass)) {
            return true;
        }
        return false;
    }
}
